<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Android签名机制详解 | 江流</title>
    <meta name="generator" content="VuePress 1.8.2">
    <link rel="icon" href="/favicon.ico">
    <script>
	  var _hmt = _hmt || [];
	  (function() {
	    var hm = document.createElement("script");
		hm.src = "https://hm.baidu.com/hm.js?d53065bd59576478808301d9b305d57a";
	    var s = document.getElementsByTagName("script")[0]; 
	    s.parentNode.insertBefore(hm, s);
	  })();
	  </script>        
	  </script>
    <meta name="description" content="我的知识存储空间，不大不小刚刚好！">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    
    <link rel="preload" href="/assets/css/0.styles.f7444686.css" as="style"><link rel="preload" href="/assets/js/app.371ed010.js" as="script"><link rel="preload" href="/assets/js/3.fe7e7b6d.js" as="script"><link rel="preload" href="/assets/js/1.adb5634c.js" as="script"><link rel="preload" href="/assets/js/25.61d7e926.js" as="script"><link rel="prefetch" href="/assets/js/10.1f1951c3.js"><link rel="prefetch" href="/assets/js/100.61abc83e.js"><link rel="prefetch" href="/assets/js/101.36b1a9cd.js"><link rel="prefetch" href="/assets/js/102.ab6aaef1.js"><link rel="prefetch" href="/assets/js/103.56b65027.js"><link rel="prefetch" href="/assets/js/104.10a9920d.js"><link rel="prefetch" href="/assets/js/105.2af1fce1.js"><link rel="prefetch" href="/assets/js/106.89d909fa.js"><link rel="prefetch" href="/assets/js/107.8a051ba9.js"><link rel="prefetch" href="/assets/js/108.8d32e2bb.js"><link rel="prefetch" href="/assets/js/109.98a9c733.js"><link rel="prefetch" href="/assets/js/11.4f1a38c2.js"><link rel="prefetch" href="/assets/js/110.cb2819bf.js"><link rel="prefetch" href="/assets/js/111.3763e438.js"><link rel="prefetch" href="/assets/js/112.437502fc.js"><link rel="prefetch" href="/assets/js/113.c84b80e9.js"><link rel="prefetch" href="/assets/js/114.e8265ab7.js"><link rel="prefetch" href="/assets/js/115.5950f39b.js"><link rel="prefetch" href="/assets/js/116.7082589d.js"><link rel="prefetch" href="/assets/js/117.9ab33002.js"><link rel="prefetch" href="/assets/js/118.b03051ef.js"><link rel="prefetch" href="/assets/js/119.31fe4216.js"><link rel="prefetch" href="/assets/js/12.397d3f87.js"><link rel="prefetch" href="/assets/js/120.4d52fd08.js"><link rel="prefetch" href="/assets/js/121.024a68dc.js"><link rel="prefetch" href="/assets/js/122.75cd9e9a.js"><link rel="prefetch" href="/assets/js/123.3060f162.js"><link rel="prefetch" href="/assets/js/124.b65e3f9b.js"><link rel="prefetch" href="/assets/js/125.87d06856.js"><link rel="prefetch" href="/assets/js/126.23d0d6ec.js"><link rel="prefetch" href="/assets/js/127.1ff7063e.js"><link rel="prefetch" href="/assets/js/128.e7a78683.js"><link rel="prefetch" href="/assets/js/129.3a378a54.js"><link rel="prefetch" href="/assets/js/13.d1346059.js"><link rel="prefetch" href="/assets/js/130.3820ccc0.js"><link rel="prefetch" href="/assets/js/14.961feb61.js"><link rel="prefetch" href="/assets/js/15.61355499.js"><link rel="prefetch" href="/assets/js/16.6759c278.js"><link rel="prefetch" href="/assets/js/17.39315298.js"><link rel="prefetch" href="/assets/js/18.9995ddc3.js"><link rel="prefetch" href="/assets/js/19.e7007f1f.js"><link rel="prefetch" href="/assets/js/20.8284a0ff.js"><link rel="prefetch" href="/assets/js/21.42efcf40.js"><link rel="prefetch" href="/assets/js/22.ce83188a.js"><link rel="prefetch" href="/assets/js/23.125b374b.js"><link rel="prefetch" href="/assets/js/24.9a24b2f6.js"><link rel="prefetch" href="/assets/js/26.4c271fce.js"><link rel="prefetch" href="/assets/js/27.08314644.js"><link rel="prefetch" href="/assets/js/28.44a30857.js"><link rel="prefetch" href="/assets/js/29.4b86327a.js"><link rel="prefetch" href="/assets/js/30.284e1438.js"><link rel="prefetch" href="/assets/js/31.c7fb218e.js"><link rel="prefetch" href="/assets/js/32.9591edea.js"><link rel="prefetch" href="/assets/js/33.52d0ee09.js"><link rel="prefetch" href="/assets/js/34.24dce9bc.js"><link rel="prefetch" href="/assets/js/35.e9e59a19.js"><link rel="prefetch" href="/assets/js/36.470d3b70.js"><link rel="prefetch" href="/assets/js/37.f43d4d57.js"><link rel="prefetch" href="/assets/js/38.bb14e7a9.js"><link rel="prefetch" href="/assets/js/39.4238fcd3.js"><link rel="prefetch" href="/assets/js/4.10111819.js"><link rel="prefetch" href="/assets/js/40.eb5830d8.js"><link rel="prefetch" href="/assets/js/41.f2de0dd7.js"><link rel="prefetch" href="/assets/js/42.ee6bc670.js"><link rel="prefetch" href="/assets/js/43.aeb80fc2.js"><link rel="prefetch" href="/assets/js/44.3dcf73d7.js"><link rel="prefetch" href="/assets/js/45.f60dd2b7.js"><link rel="prefetch" href="/assets/js/46.3ffd9a49.js"><link rel="prefetch" href="/assets/js/47.0629cfd8.js"><link rel="prefetch" href="/assets/js/48.4064d363.js"><link rel="prefetch" href="/assets/js/49.e5cd2868.js"><link rel="prefetch" href="/assets/js/5.bbaa5544.js"><link rel="prefetch" href="/assets/js/50.e1524d44.js"><link rel="prefetch" href="/assets/js/51.ef5d60b4.js"><link rel="prefetch" href="/assets/js/52.9e15bbc1.js"><link rel="prefetch" href="/assets/js/53.9e53a9b3.js"><link rel="prefetch" href="/assets/js/54.bda58726.js"><link rel="prefetch" href="/assets/js/55.cdc29215.js"><link rel="prefetch" href="/assets/js/56.3f0607d3.js"><link rel="prefetch" href="/assets/js/57.b45ba0fb.js"><link rel="prefetch" href="/assets/js/58.12450427.js"><link rel="prefetch" href="/assets/js/59.33096dcc.js"><link rel="prefetch" href="/assets/js/6.8df5fb44.js"><link rel="prefetch" href="/assets/js/60.98645e3a.js"><link rel="prefetch" href="/assets/js/61.413d9151.js"><link rel="prefetch" href="/assets/js/62.cc3b9f5a.js"><link rel="prefetch" href="/assets/js/63.05784f29.js"><link rel="prefetch" href="/assets/js/64.d6d2548e.js"><link rel="prefetch" href="/assets/js/65.dfa3ba68.js"><link rel="prefetch" href="/assets/js/66.bb4f5fde.js"><link rel="prefetch" href="/assets/js/67.2c282a4f.js"><link rel="prefetch" href="/assets/js/68.ce435a87.js"><link rel="prefetch" href="/assets/js/69.e439f6e2.js"><link rel="prefetch" href="/assets/js/7.751d2655.js"><link rel="prefetch" href="/assets/js/70.270cc6b6.js"><link rel="prefetch" href="/assets/js/71.ad1e4a0b.js"><link rel="prefetch" href="/assets/js/72.0adf2b53.js"><link rel="prefetch" href="/assets/js/73.72e019b5.js"><link rel="prefetch" href="/assets/js/74.a4eb5a17.js"><link rel="prefetch" href="/assets/js/75.6412ff6f.js"><link rel="prefetch" href="/assets/js/76.7f462fc1.js"><link rel="prefetch" href="/assets/js/77.16502231.js"><link rel="prefetch" href="/assets/js/78.3082b00c.js"><link rel="prefetch" href="/assets/js/79.4f375500.js"><link rel="prefetch" href="/assets/js/8.6c3fab20.js"><link rel="prefetch" href="/assets/js/80.0f2472d6.js"><link rel="prefetch" href="/assets/js/81.136b6c89.js"><link rel="prefetch" href="/assets/js/82.6f5b7de7.js"><link rel="prefetch" href="/assets/js/83.0eb8579b.js"><link rel="prefetch" href="/assets/js/84.b50a036d.js"><link rel="prefetch" href="/assets/js/85.e7b43c1a.js"><link rel="prefetch" href="/assets/js/86.0cbd7e7b.js"><link rel="prefetch" href="/assets/js/87.647fd8a9.js"><link rel="prefetch" href="/assets/js/88.be268039.js"><link rel="prefetch" href="/assets/js/89.2c0b397f.js"><link rel="prefetch" href="/assets/js/9.ace3bd0d.js"><link rel="prefetch" href="/assets/js/90.fb93a187.js"><link rel="prefetch" href="/assets/js/91.45160178.js"><link rel="prefetch" href="/assets/js/92.4e47d015.js"><link rel="prefetch" href="/assets/js/93.2f9bae72.js"><link rel="prefetch" href="/assets/js/94.c68d9319.js"><link rel="prefetch" href="/assets/js/95.a6932cad.js"><link rel="prefetch" href="/assets/js/96.dea31892.js"><link rel="prefetch" href="/assets/js/97.e8c73484.js"><link rel="prefetch" href="/assets/js/98.bf38dbf7.js"><link rel="prefetch" href="/assets/js/99.edd450af.js">
    <link rel="stylesheet" href="/assets/css/0.styles.f7444686.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container no-sidebar" data-v-6f8f7dda><div data-v-6f8f7dda><div class="password-shadow password-wrapper-out" style="display:none;" data-v-f68096de data-v-6f8f7dda data-v-6f8f7dda><h3 class="title" data-v-f68096de>江流</h3> <p class="description" data-v-f68096de>我的知识存储空间，不大不小刚刚好！</p> <label id="box" class="inputBox" data-v-f68096de><input type="password" value="" data-v-f68096de> <span data-v-f68096de>Konck! Knock!</span> <button data-v-f68096de>OK</button></label> <div class="footer" data-v-f68096de><span data-v-f68096de><i class="iconfont reco-theme" data-v-f68096de></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-f68096de>vuePress-theme-reco</a></span> <span data-v-f68096de><i class="iconfont reco-copyright" data-v-f68096de></i> <a data-v-f68096de><span data-v-f68096de>江流</span>
          
        <span data-v-f68096de>2018 - </span>
        2023
      </a></span></div></div> <div class="hide" data-v-6f8f7dda><header class="navbar" data-v-6f8f7dda><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/logo.png" alt="江流" class="logo"> <span class="site-name">江流</span></a> <div class="links"><div class="color-picker"><a class="color-button"><i class="iconfont reco-color"></i></a> <div class="color-picker-menu" style="display:none;"><div class="mode-options"><h4 class="title">Choose mode</h4> <ul class="color-mode-options"><li class="dark">dark</li><li class="auto active">auto</li><li class="light">light</li></ul></div></div></div> <div class="search-box"><i class="iconfont reco-search"></i> <input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/复盘/" class="nav-link"><i class="undefined"></i>
  复盘
</a></li><li class="dropdown-item"><!----> <a href="/categories/学习总结/" class="nav-link"><i class="undefined"></i>
  学习总结
</a></li><li class="dropdown-item"><!----> <a href="/categories/踩坑/" class="nav-link"><i class="undefined"></i>
  踩坑
</a></li><li class="dropdown-item"><!----> <a href="/categories/工具/" class="nav-link"><i class="undefined"></i>
  工具
</a></li><li class="dropdown-item"><!----> <a href="/categories/Linux/" class="nav-link"><i class="undefined"></i>
  Linux
</a></li><li class="dropdown-item"><!----> <a href="/categories/算法/" class="nav-link"><i class="undefined"></i>
  算法
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java基础/" class="nav-link"><i class="undefined"></i>
  Java基础
</a></li><li class="dropdown-item"><!----> <a href="/categories/高并发编程/" class="nav-link"><i class="undefined"></i>
  高并发编程
</a></li><li class="dropdown-item"><!----> <a href="/categories/数据库/" class="nav-link"><i class="undefined"></i>
  数据库
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java框架/" class="nav-link"><i class="undefined"></i>
  Java框架
</a></li><li class="dropdown-item"><!----> <a href="/categories/学习笔记/" class="nav-link"><i class="undefined"></i>
  学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/categories/框架学习/" class="nav-link"><i class="undefined"></i>
  框架学习
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java项目/" class="nav-link"><i class="undefined"></i>
  Java项目
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  标签
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  时间轴
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-note"></i>
      学习笔记
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/docs/major-notes/java/" class="nav-link"><i class="undefined"></i>
  Java笔记整理
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/juc/" class="nav-link"><i class="undefined"></i>
  JUC学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/jvm.html" class="nav-link"><i class="undefined"></i>
  JVM核心知识
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/vue.html" class="nav-link"><i class="undefined"></i>
  Vue入门笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/linux/" class="nav-link"><i class="undefined"></i>
  Linux笔记整理
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/redis/" class="nav-link"><i class="undefined"></i>
  Redis学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/springcloud/" class="nav-link"><i class="undefined"></i>
  SpringCloud笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/spring/" class="nav-link"><i class="undefined"></i>
  Spring学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/shiro.html" class="nav-link"><i class="undefined"></i>
  Shiro学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/android-notes/" class="nav-link"><i class="undefined"></i>
  Android学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/project-notes/" class="nav-link"><i class="undefined"></i>
  项目笔记
</a></li></ul></div></div><div class="nav-item"><a href="/docs/collections/" class="nav-link"><i class="iconfont reco-collection"></i>
  收藏夹
</a></div><div class="nav-item"><a href="/docs/life-logs/" class="nav-link"><i class="iconfont reco-camera"></i>
  生活日志
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-browse"></i>
      常用网站
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="http://www.cxysite.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  程序员导航
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.nowcoder.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  牛客网
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://leetcode-cn.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  力扣
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-person"></i>
      私藏博客
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://docker.easydoc.net" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  快速入门docker
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.nowcoder.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  牛客网
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://leetcode-cn.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  力扣
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      联系方式
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://gitee.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-gitee"></i>
  Gitee
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://github.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-github"></i>
  GitHub
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav></div></header> <div class="sidebar-mask" data-v-6f8f7dda></div> <aside class="sidebar" data-v-6f8f7dda><div class="personal-info-wrapper" data-v-cc06b9e8 data-v-6f8f7dda><img src="/avatar.png" alt="author-avatar" class="personal-img" data-v-cc06b9e8> <h3 class="name" data-v-cc06b9e8>
    江流
  </h3> <div class="num" data-v-cc06b9e8><div data-v-cc06b9e8><h3 data-v-cc06b9e8>104</h3> <h6 data-v-cc06b9e8>Articles</h6></div> <div data-v-cc06b9e8><h3 data-v-cc06b9e8>21</h3> <h6 data-v-cc06b9e8>Tags</h6></div></div> <ul class="social-links" data-v-cc06b9e8></ul> <hr data-v-cc06b9e8></div> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/复盘/" class="nav-link"><i class="undefined"></i>
  复盘
</a></li><li class="dropdown-item"><!----> <a href="/categories/学习总结/" class="nav-link"><i class="undefined"></i>
  学习总结
</a></li><li class="dropdown-item"><!----> <a href="/categories/踩坑/" class="nav-link"><i class="undefined"></i>
  踩坑
</a></li><li class="dropdown-item"><!----> <a href="/categories/工具/" class="nav-link"><i class="undefined"></i>
  工具
</a></li><li class="dropdown-item"><!----> <a href="/categories/Linux/" class="nav-link"><i class="undefined"></i>
  Linux
</a></li><li class="dropdown-item"><!----> <a href="/categories/算法/" class="nav-link"><i class="undefined"></i>
  算法
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java基础/" class="nav-link"><i class="undefined"></i>
  Java基础
</a></li><li class="dropdown-item"><!----> <a href="/categories/高并发编程/" class="nav-link"><i class="undefined"></i>
  高并发编程
</a></li><li class="dropdown-item"><!----> <a href="/categories/数据库/" class="nav-link"><i class="undefined"></i>
  数据库
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java框架/" class="nav-link"><i class="undefined"></i>
  Java框架
</a></li><li class="dropdown-item"><!----> <a href="/categories/学习笔记/" class="nav-link"><i class="undefined"></i>
  学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/categories/框架学习/" class="nav-link"><i class="undefined"></i>
  框架学习
</a></li><li class="dropdown-item"><!----> <a href="/categories/Java项目/" class="nav-link"><i class="undefined"></i>
  Java项目
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  标签
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  时间轴
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-note"></i>
      学习笔记
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/docs/major-notes/java/" class="nav-link"><i class="undefined"></i>
  Java笔记整理
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/juc/" class="nav-link"><i class="undefined"></i>
  JUC学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/jvm.html" class="nav-link"><i class="undefined"></i>
  JVM核心知识
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/vue.html" class="nav-link"><i class="undefined"></i>
  Vue入门笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/linux/" class="nav-link"><i class="undefined"></i>
  Linux笔记整理
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/redis/" class="nav-link"><i class="undefined"></i>
  Redis学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/springcloud/" class="nav-link"><i class="undefined"></i>
  SpringCloud笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/major-notes/spring/" class="nav-link"><i class="undefined"></i>
  Spring学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/more-notes/shiro.html" class="nav-link"><i class="undefined"></i>
  Shiro学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/android-notes/" class="nav-link"><i class="undefined"></i>
  Android学习笔记
</a></li><li class="dropdown-item"><!----> <a href="/docs/project-notes/" class="nav-link"><i class="undefined"></i>
  项目笔记
</a></li></ul></div></div><div class="nav-item"><a href="/docs/collections/" class="nav-link"><i class="iconfont reco-collection"></i>
  收藏夹
</a></div><div class="nav-item"><a href="/docs/life-logs/" class="nav-link"><i class="iconfont reco-camera"></i>
  生活日志
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-browse"></i>
      常用网站
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="http://www.cxysite.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  程序员导航
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.nowcoder.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  牛客网
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://leetcode-cn.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  力扣
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-person"></i>
      私藏博客
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://docker.easydoc.net" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  快速入门docker
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://www.nowcoder.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  牛客网
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://leetcode-cn.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="undefined"></i>
  力扣
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-message"></i>
      联系方式
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://gitee.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-gitee"></i>
  Gitee
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://github.com/" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-github"></i>
  GitHub
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav> <!----> </aside> <div class="password-shadow password-wrapper-in" style="display:none;" data-v-f68096de data-v-6f8f7dda><h3 class="title" data-v-f68096de>Android签名机制详解</h3> <!----> <label id="box" class="inputBox" data-v-f68096de><input type="password" value="" data-v-f68096de> <span data-v-f68096de>Konck! Knock!</span> <button data-v-f68096de>OK</button></label> <div class="footer" data-v-f68096de><span data-v-f68096de><i class="iconfont reco-theme" data-v-f68096de></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-f68096de>vuePress-theme-reco</a></span> <span data-v-f68096de><i class="iconfont reco-copyright" data-v-f68096de></i> <a data-v-f68096de><span data-v-f68096de>江流</span>
          
        <span data-v-f68096de>2018 - </span>
        2023
      </a></span></div></div> <div data-v-6f8f7dda><div data-v-6f8f7dda><main class="page"><section style="display:;"><div class="page-title"><h1 class="title">Android签名机制详解</h1> <div data-v-1e62957f><i class="iconfont reco-account" data-v-1e62957f><span data-v-1e62957f>江流</span></i> <i class="iconfont reco-date" data-v-1e62957f><span data-v-1e62957f>8/19/2022</span></i> <!----> <i class="tags iconfont reco-tag" data-v-1e62957f><span class="tag-item" data-v-1e62957f>Android</span></i></div></div> <div class="theme-reco-content content__default"><p>近期由于工作需要在学习 Android 的签名机制，因为没有现成资料，只能通过开发者文档和阅读博客的方式对 Android 签名机制进行大致了解。过程中查阅到的资料相对零散，不够系统和全面，对于刚入门 Android 学习的小白来说，要快速掌握其内容着实是一大挑战。本文建立与各位前辈的基础之上，加之自己在学习过程中的理解，对 Android 签名机制所涉及的内容进行一个系统梳理，一个是对自己学习过程的一个复盘，二也希望能够对刚入门的同学有所启发和帮助。</p> <h2 id="签名机制"><a href="#签名机制" class="header-anchor">#</a> 签名机制</h2> <p>签名机制本质是对信息的一种加密措施，为的是保证信息的合法性。在说明签名机制之前我们先来了解其中涉及到的四个概念：数字摘要、非对称加密、数字签名、数字证书。</p> <ul><li><p><strong>数字摘要：</strong> 采用单向 Hash 函数将需要加密的明文生成一串固定长度的密文，这一串密文又称为数字指纹，其具有唯一性和不可逆性。数字摘要的生成取决于 Hash 算法，常用的 Hash 算法有 MD5 、SHA1、SHA256，MD5 的长度是128位，SHA1的长度是160位，SHA256的长度为256位。</p></li> <li><p><strong>非对称加密：</strong> 非对称加密即加密和解密的密钥不同，其中涉及到一对密钥：公钥和私钥。当一消息通过公钥加密。解密只能通过其对应的私钥完成；当消息通过私钥加密，只能通过其对应的公钥解密。</p></li></ul> <p><img src="http://images.xbnote.top/images/202208162221419.png" alt=""></p> <ul><li><p><strong>数字签名：</strong> 通过公钥对明文进行加密得到的一段数字串，用于保证信息来源的真实性和消息的完整性。</p></li> <li><p><strong>数字证书：</strong> 校验过程有个前提：接收方必须要知道发送方的公钥和所使用的算法。如果数字签名和公钥一起被篡改，接收方无法得知，还是会校验通过。如何保证公钥的可靠性呢？答案是数字证书，数字证书是身份认证机构（Certificate Authority）颁发的，包含了以下信息：</p> <ul><li>证书颁发机构</li> <li>证书颁发机构签名</li> <li>证书绑定的服务器域名</li> <li>证书版本、有效期</li> <li>签名使用的加密算法（非对称算法，如RSA）、公钥 等</li></ul> <p>接收方收到消息后，先向CA验证证书的合法性（根据证书的签名、绑定的域名等信息。CA机构是权威的，可以保证这个过程的可靠性）再进行签名校验。</p></li></ul> <p>了解了以上概念，我们来看一下签名的过程：</p> <p><img src="http://images.xbnote.top/images/202208162221367.png" alt=""></p> <p>签名过程就是 <strong>使用非对称加密算法用私钥对摘要进行加密</strong> 的过程。我们再看一下签名的校验过程：</p> <p><img src="http://images.xbnote.top/images/202208162221390.png" alt=""></p> <p>校验过程与签名过程相反，消息接受者 <strong>通过数字证书中的公钥信息和非对称算法对签名进行解密，然后用相同的 Hash 算法对明文进行摘要计算，再将计算出的摘要与解密后得到的摘要进行对比，以校验消息的合法性。</strong></p> <h2 id="android签名机制"><a href="#android签名机制" class="header-anchor">#</a> Android签名机制</h2> <p>Android 签名与传统签名机制有些许不同。</p> <p>首先，APK 的数字证书没有通过 CA 机构申请，而是开发者自定义的，且 Android 在安装 APK 时并不会校验证书本身的合法性，只是从中提取公钥和加密算法。正因如此，第三方 APK 在重新签名后依然能在没有安装这个 APK 系统中继续安装。</p> <p>此外，Android 在对 APK 签名时并没有直接指定私钥、公钥和数字证书，而是使用 keystore 文件，将这些信息都包含在 keystore 文件中。</p> <h3 id="keystore"><a href="#keystore" class="header-anchor">#</a> keystore</h3> <p>根据编码格式不同，keystore 文件分为很多种，Android 使用的是 Java 标准 keystore 格式 JKS (Java Key Storage)，所以通过 Android Studio 导出的 keystore 文件是以 .jks 结尾的。</p> <p>keystore 使用的证书标准是 X.509，X.509 标准也有多种编码格式，常用的有两种：pem（Privacy Enhanced Mail）和 der（Distinguished Encoding Rules）。jks 使用的是 der 格式，Android 也支持直接使用 pem 格式的证书进行签名。</p> <p>两种证书编码格式的区别：</p> <ul><li><p>DER（Distinguished Encoding Rules）：二进制格式，所有类型的证书和私钥都可以存储为der格式；</p></li> <li><p>PEM（Privacy Enhanced Mail）：base64编码，内容以—–BEGIN xxx—– 开头，以—–END xxx—– 结尾。</p></li></ul> <h3 id="密钥库生成"><a href="#密钥库生成" class="header-anchor">#</a> 密钥库生成</h3> <p>keytool 是 Java 自带的证书生成工具，位于 JAVA_HOME 的 bin 目录下安装目录下，以下是使用 keytool 生成密钥库的简单操作。</p> <ol><li>使用 JDK 自带的 keytool 工具，执行以下指令</li></ol> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code>keytool <span class="token parameter variable">-genkeypair</span> <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> <span class="token parameter variable">-alias</span> <span class="token operator">&lt;</span>alis<span class="token operator">&gt;</span> <span class="token parameter variable">-validity</span> <span class="token operator">&lt;</span>period-of-validity<span class="token operator">&gt;</span> <span class="token parameter variable">-keyalg</span> RSA
<span class="token comment">#例：生成密钥库test-keystore.jks，其有效期为365天</span>
keytool <span class="token parameter variable">-genkeypair</span> <span class="token parameter variable">-keystore</span> test-keystore.jks <span class="token parameter variable">-alias</span> mytest <span class="token parameter variable">-validity</span> <span class="token number">365</span>
<span class="token comment">#test-keystore.jks：密钥库名 </span>
<span class="token comment">#mytest：密钥别名,作用在于当密钥库可以存在多个密钥对，可以区分不同密钥对，算法只支持RAS和DSA</span>
<span class="token comment">#android：密钥库密码</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><blockquote><p>默认情况下直接通过Android Studio在Run或Debug时，会使用主目录下的默认密钥库 .android\debug.keystore 对 App 签名</p></blockquote> <ol start="2"><li>创建完毕后，可以查看密钥对信息</li></ol> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code>keytool <span class="token parameter variable">-v</span> <span class="token parameter variable">-list</span> <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span>
<span class="token comment">#例：以上条命令生成密钥库test-keystore.jks文件为例</span>
keytool <span class="token parameter variable">-v</span> <span class="token parameter variable">-list</span> <span class="token parameter variable">-keystore</span> test-keystore.jks
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>keytool  的几个常用命令我也在这做了一个简单整理，如下</p> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code><span class="token comment">#生成密钥对</span>
keytool <span class="token parameter variable">-genkeypair</span> <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystor-name<span class="token operator">&gt;</span> <span class="token parameter variable">-alias</span> <span class="token operator">&lt;</span>alis<span class="token operator">&gt;</span> <span class="token parameter variable">-validity</span> <span class="token operator">&lt;</span>period-of-validity<span class="token operator">&gt;</span> <span class="token parameter variable">-keyalg</span> RSA
<span class="token comment">#查看密钥库详情</span>
keytool <span class="token parameter variable">-v</span> <span class="token parameter variable">-list</span> <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span>
<span class="token comment">#查看密钥库证书</span>
keytool <span class="token parameter variable">-list</span> <span class="token parameter variable">-rfc</span> <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> <span class="token parameter variable">-storepass</span> <span class="token operator">&lt;</span>keystore-password<span class="token operator">&gt;</span>
<span class="token comment">#导出公钥</span>
keytool <span class="token parameter variable">-list</span> <span class="token parameter variable">-rfc</span> <span class="token parameter variable">--keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> <span class="token operator">|</span> openssl x509 <span class="token parameter variable">-inform</span> pem <span class="token parameter variable">-pubkey</span>
<span class="token comment">#v1签名校验</span>
keytool <span class="token parameter variable">-printcert</span> <span class="token parameter variable">-jarfile</span> xxxx.apk
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>关于 keytool 的详细使用教程，可以看一下这篇文章：<a href="https://blog.csdn.net/kunlyy/article/details/105399255" target="_blank" rel="noopener noreferrer">使用JDK自带keytool生成证书<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p> <h2 id="android签名方案"><a href="#android签名方案" class="header-anchor">#</a> Android签名方案</h2> <p>截至目前，Android 已推出 4 种签名方案，即 V1、V2、V3、V3。总体上我们可以将其归类为 V1 和 V2+，V2 版本之后的设计原理与 V2 基本保持一致，因此我们重点需要理解 V2 签名的设计原理。那么接下来，我们将一步步探究 Android 签名机制的实现原理。</p> <h3 id="签名方案-v1"><a href="#签名方案-v1" class="header-anchor">#</a> 签名方案—V1</h3> <p>了解 V1 签名原理之前，我们先来看一下 APK 的目录结构。找到任意一个已经进行签名的 APK 通过解压软件打开，打开后我们可以看到一个 MATE-INF 目录，点击进入，我们可以看到 MANIFEST.MF、*.SF 和 *.RSA 三个文件，如下图所示：</p> <p><img src="http://images.xbnote.top/images/202208162221535.png" alt="image-20220815161542947"></p> <p>接下来，我们来新认识一个新的工具— jarsigner，其位于 JAVA_HOME/bin/jarsigner，它是 Android 早期用于对 APK 进行签名的工具。找一个 APK 包，删除其 MATE-INF 目录，执行以下命令。</p> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code><span class="token comment">#对xxx.apk进行签名</span>
jarsigner <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> xxx.apk <span class="token operator">&lt;</span>alias<span class="token operator">&gt;</span>
<span class="token comment">#兼容android 4.1以下，若密钥库中有多个密钥对，则必须指定密钥别名。</span>
jarsigner <span class="token parameter variable">-keystore</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> <span class="token parameter variable">-digestalg</span> SHA1 <span class="token parameter variable">-sigalg</span> SHA1withRSA xxx.apk <span class="token operator">&lt;</span>alias<span class="token operator">&gt;</span>
<span class="token comment">#例:使用test-keystore.jks对test.apk进行签名</span>
jarsigner <span class="token parameter variable">-keystore</span> test-keystore.jks test.apk test-signed.apk
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>可以发现，APK 包下重新生成了 MATE_INF 目录，同时 MATE_INF 目录下依然保存了 MANIFEST.MF、*.SF 和 *.RSA  三个文件。我们可以分别查看一下这三个文件内容：</p> <ul><li>MAINFEST.MF内容</li></ul> <p><img src="http://images.xbnote.top/images/202208162221680.png" alt="image-20220815165340924"></p> <ul><li>*.SF 内容</li></ul> <p><img src="http://images.xbnote.top/images/202208162221067.png" alt="image-20220815165527958"></p> <ul><li>*.RSA 是二进制文件，无法直接查看，我们使用 keytool 在命令行窗口进行查看：</li></ul> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code>  keytool <span class="token parameter variable">-printcert</span> <span class="token parameter variable">-file</span> *.RSA
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p><img src="http://images.xbnote.top/images/202208162221242.png" alt=""></p> <p>通过以上观察，我们以上三个文件的结构有一个大体印象。接下来，我们来正式认识这几个文件。</p> <ul><li><p>MANIFEST.MF：保存了 APK 除 META-INF 目录外的所有文件对应的数字摘要信息。在签名过程中，freamwork 首先会遍历 APK 包下的所有文件(entry)，然后逐个生成 SHA256（JDK7.0之前采用 SHA1算法）数字摘要信息，再用 base64 进行编码之后将生成的摘要写入 MANIFEST.MF 文件中。</p></li> <li><p>*.SF：对前一步生成的 MANIFEST.MF，使用 SHA256-RSA（JDK7.0之前采用 SHA1算法） 算法，用私钥进行签名，其中的值是对清单文件里的 SHA256 再进行 SHA256 后再次 base64 得到。</p> <blockquote><p>RSA 是一种非对称加密算法。用私钥通过 RSA 算法对摘要信息进行加密。在安装时只能使用公钥才能解密它。解密之后，将它与未加密的摘要信息进行对比，如果相符，则表明内容没有被异常修改。</p></blockquote></li> <li><p>*.RAS：生成 MANIFEST.MF 没有使用密钥信息，而生成 <em>.SF 文件使用了私钥文件。</em>.RSA 文件中保存了公钥、所采用的加密算法等信息 ，framework 会解析这个 RAS 文件。</p></li></ul> <p><img src="http://images.xbnote.top/images/202208162221390.png" alt=""></p> <blockquote><p>对中间过程中的签名校验，可以参考这篇博客：<a href="https://blog.csdn.net/zwjemperor/article/details/80877305" target="_blank" rel="noopener noreferrer"> APK签名机制之——JAR签名机制详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>在校验过程，freamwork 首先会读取 *.RSA 中的公钥信息和加密算法信息，然后对 *.SF 文件的签名逐一解析，再与签名过程中相同的 Hash 算法计算出 APK 中非 MATE_INFO 目录下的各个文件的摘要，并与签名解析出的对应摘要对比，以此确定 APK 的完整性。</p> <p>通过以上分析，我们对 V1 签名有了一个较为基础的认识和理解。V1 是基于 jar 的签名方式，该方案两个明显的缺陷：</p> <ol><li>校验效率低，遍历每个文件需要进行 IO 操作，对于一个庞大的项目来说效率非常不可观；</li> <li>安全性低，由于校验时不对 META-INF 目录下的文件进行校验，因此存在一定的安全隐患。</li></ol> <blockquote><p>关于 JAR 签名文件的说明，可以看一下官网介绍：<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File" target="_blank" rel="noopener noreferrer">JAR 文件规范 (oracle.com)<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <h3 id="签名方案-v2"><a href="#签名方案-v2" class="header-anchor">#</a> 签名方案—V2</h3> <p>由于 V1 签名方案在校验 APK 完整性上并不完善，且存在效率问题，谷歌在 Android 7.0 引入了 V2 签名方案。V2 的设计原理与 V1 相较也发生了根本性变化。在了解 V2 的签名原理之前，我们先来认识一下 ZIP 文件结构，这对我们理解 V2 签名的工作流程及其重要。</p> <h4 id="认识zip文件"><a href="#认识zip文件" class="header-anchor">#</a> 认识ZIP文件</h4> <p>ZIP 文件结构如下：</p> <p><img src="http://images.xbnote.top/images/202208162221294.png" alt=""></p> <p>ZIP 文件结构包括三大部分：<strong>数据区、中央目录和中央结尾记录</strong>，数据区包含了所有的文件记录，中央目录存储了所有本地文件头信息，中央目录结尾存放了中央目录的相关信息。</p> <p>在解析 ZIP 文件时，程序首先会找到 ZIP 文件的 <strong>中央目录结尾</strong>，然后在中央目录结尾中找到 <strong>核心目录的起始偏移量</strong> 定位到 <strong>中央目录起始位置</strong>，然后开始遍历中央目录里的 <strong>本地文件头</strong> ，根据本地文件头中的 <strong>文件头的相对位移</strong> 定位到相应的本地文件。</p> <blockquote><p>关于 ZIP 文件结构解析过程的具体细节，可以参考这篇文章：<a href="https://blog.csdn.net/qq_43278826/article/details/118436116" target="_blank" rel="noopener noreferrer">Zip文件格式详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <p>无论是 jar 包还是 APK 包，其本质都是 zip 文件，因此我们在解析 jar 和 APK 时就是在解析 zip。而 V2 的实现原理就是对 ZIP 文件进行一定处理。</p> <h4 id="v2-方案解析"><a href="#v2-方案解析" class="header-anchor">#</a> V2 方案解析</h4> <p>V2 是一种全文件签名方案，在使用 V2 方案对 APK 进行签名时，会在 APK 文件中插入一个 <strong>APK 签名分块</strong>，该分块位于 <strong>ZIP 中央目录</strong> 部分之前并紧邻该部分。在<strong>APK 签名分块</strong> 内，v2 签名和签名者身份信息会存储在 APK 签名方案 v2 分块中。</p> <p><img src="http://images.xbnote.top/images/202208162221426.png" alt="image-20220816113002000"></p> <h5 id="apk-签名分块"><a href="#apk-签名分块" class="header-anchor">#</a> APK 签名分块</h5> <p><img src="http://images.xbnote.top/images/202208162222685.png" alt="">在解析 APK 文件时，首先要通过以下方法找到 <strong>ZIP 中央目录</strong> 的起始位置：在文件末尾找到 <strong>ZIP 中央目录结尾</strong> 记录，然后从该记录中读取 <strong>中央目录的起始偏移量</strong>  。通过 <strong>magic</strong> 值，可以快速确定中央目录前方可能是 <strong>APK 签名分块</strong>。然后，通过 <strong>size of block</strong> 值，可以高效地找到该分块在文件中的起始位置。</p> <p>在解译该分块时，程序将忽略 ID 未知的ID-值对。</p> <h5 id="apk-签名方案-v2-分块"><a href="#apk-签名方案-v2-分块" class="header-anchor">#</a> APK 签名方案 V2 分块</h5> <p>APK 由一个或多个签名者/身份签名，每个签名者/身份均由一个签名密钥来表示。该信息会以APK 签名方案 v2 分块的形式存储。对于每个签名者，都会存储以下信息：</p> <ul><li>（签名算法、摘要、签名）元组。摘要会存储起来，以便将签名验证和 APK 内容完整性检查拆开进行。</li> <li>表示签名者身份的 X.509 证书链。</li> <li>采用键值对形式的其他属性。</li></ul> <p><img src="http://images.xbnote.top/images/202208162222771.png" alt=""></p> <p><strong>APK 签名方案 v2 分块</strong> 存储在 <strong>APK 签名分块</strong> 内，ID 为  <strong>0x7109871a</strong>。APK 签名方案 v2 分的格式如下</p> <p><img src="http://images.xbnote.top/images/202208162222776.png" alt=""></p> <h5 id="apk-完整性保护"><a href="#apk-完整性保护" class="header-anchor">#</a> APK 完整性保护</h5> <p>签名后的 APK 文件包含以下四个部分：</p> <p><img src="https://source.android.google.cn/security/images/apk-sections.png" alt="签名后的各个 APK 部分"></p> <p>APK 签名方案 v2 负责保护第 1、3、4 部分的完整性，以及第 2 部分包含的 <strong>APK 签名方案 v2 分块</strong> 中的 <strong>signed data 分块</strong> 的完整性。</p> <p>第 1、3 和 4 部分的完整性通过其内容的一个或多个摘要来保护，这些摘要存储在 <strong>signed data</strong> 分块中，而 <strong>这些分块则通过一个或多个签名来保护</strong> 。</p> <p>第 1、3 和 4 部分的摘要采用以下计算方式，类似于两级 <a href="https://en.wikipedia.org/wiki/Merkle_tree" target="_blank" rel="noopener noreferrer">Merkle 树<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。每个部分都会被拆分成多个大小为 1MB（220 个字节）的连续块。每个部分的最后一个块可能会短一些。每个块的摘要均通过 <strong>字节 0xa5 的串联、块的长度（采用小端字节序的 uint32 值，以字节数计）和块的内容</strong> 进行计算。顶级摘要通过  <strong>字节0x5a 的串联、块数（采用小端字节序的 uint32 值）以及块的摘要的连接（按照块在 APK 中显示的顺序）</strong> 进行计算。摘要以分块方式计算，以便通过并行处理来加快计算速度。</p> <p><img src="https://source.android.google.cn/security/images/apk-integrity-protection.png" alt="APK 摘要"></p> <p>由于第 4 部分（ZIP 中央目录结尾）包含 ZIP 中央目录的偏移量，因此该部分的保护比较复杂。当 <strong>APK 签名分块</strong> 的大小发生变化（例如，添加了新签名）时，偏移量也会随之改变。因此，在通过 <strong>ZIP 中央目录结尾</strong> 计算摘要时，必须将包含 <strong>ZIP 中央目录</strong> 偏移量的字段视为包含 <strong>APK 签名分块</strong> 的偏移量。</p> <h5 id="签名验证"><a href="#签名验证" class="header-anchor">#</a> 签名验证</h5> <p>在 Android 7.0 及更高版本中，可以根据 APK 签名方案 v2+ 或 JAR 签名（v1 方案）验证 APK。更低版本的平台会忽略 v2 签名，仅验证 v1 签名。APK 签名验证过程如下图所示。（新步骤以红色显示）</p> <p><img src="https://source.android.google.cn/security/images/apk-v2-validation.png" alt="APK 签名验证过程"></p> <ol><li><p>找到 APK 签名分块并验证以下内容：</p> <ol><li>APK 签名分块的两个大小字段包含相同的值。</li> <li>ZIP 中央目录结尾紧跟在ZIP 中央目录记录后面。</li> <li>ZIP 中央目录结尾之后没有任何数据。</li></ol></li> <li><p>找到 APK 签名分块中的第一个 APK 签名方案 v2 分块。如果 v2 分块存在，则继续执行第 3 步。否则，回退至使用 v1 方案验证 APK。</p></li> <li><p>对 APK 签名方案 v2 分块中的每个 signer，执行以下操作：</p> <ol><li>从 <strong>signatures</strong> 中选择安全系数最高的受支持 <strong>signature algorithm ID</strong>。安全系数排序取决于各个实现/平台版本。</li></ol></li> <li><p>使用 <strong>public key</strong> 并对照 <strong>signed data</strong> 验证 <strong>signatures</strong> 中对应的 <strong>signature</strong>。（现在可以安全地解析 <strong>signed data</strong> 了）
3. 验证 <strong>digests</strong> 和 <strong>signatures</strong> 中的签名算法 ID 列表（有序列表）是否相同。（这是为了防止删除/添加签名）
4. 使用签名算法所用的同一种摘要算法计算 APK 内容的摘要。
5. 验证计算出的摘要是否与 <strong>digests</strong> 中对应的 <strong>digest</strong> 一致。</p></li> <li><p>验证 <strong>certificates</strong> 中第一个 <strong>certificate</strong> 的 SubjectPublicKeyInfo 是否与 <strong>public key</strong> 相同。</p></li> <li><p>如果找到了至少一个 <strong>signer</strong>，并且对于每个找到的 <strong>signer</strong>，第 3 步都取得了成功，APK 验证将会成功。</p></li></ol> <blockquote><p>注意：如果第 3 步或第 4 步失败了，则不得使用 v1 方案验证 APK。</p></blockquote> <p>对于以上校验过程，我们可以大致归纳为校验校验 APK 文件格式、找 V2 签名分块、对 V2 签名方案的 signer 分块的签名数据进行校验。其中在对 signer 分块的签名校验过程相对于其他几个过程涉及到更多专业术语，不容易看懂，我们把这个过程用图解方式进行解释。</p> <p><img src="http://images.xbnote.top/images/202208162223192.png" alt=""></p> <p>上述内容基本来自开发者文档：<a href="https://source.android.google.cn/security/apksigning/v2" target="_blank" rel="noopener noreferrer">Andorid 开发者文档—APK 签名方案 v2<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>，另外，<a href="https://blog.csdn.net/zwjemperor/article/details/81051120" target="_blank" rel="noopener noreferrer">V2签名机制详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> 这篇文章也对我有很大的启发。</p> <h4 id="apksigner"><a href="#apksigner" class="header-anchor">#</a> apksigner</h4> <p>apksigner 是 Google 官方提供的针对 Android APK 签名及验证的专用工具， 该工具位于 Android SDK/build-tools/SDK 版本 /apksigner.bat。</p> <div class="language-shell line-numbers-mode"><pre class="language-shell"><code>apksigner sign <span class="token parameter variable">--ks</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> --ks-key-alias <span class="token operator">&lt;</span>alias<span class="token operator">&gt;</span> xxx.apk
<span class="token comment">#禁用v2,若密钥库中有多个密钥对,则必须指定密钥别名</span>
apksigner sign --v2-signing-enabled <span class="token boolean">false</span> --v1-signing-enabled <span class="token boolean">true</span> <span class="token parameter variable">--ks</span> <span class="token operator">&lt;</span>keystore-name<span class="token operator">&gt;</span> xxx.apk
<span class="token comment">#例：禁用V1签名,使用V2对test.apk进行签名</span>
apksigner sign --v1-signing-enabled <span class="token boolean">false</span> <span class="token parameter variable">--ks</span> debug.keystore --ks-key-alias debugkey test.apk
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>apksigner 除了支持使用 keystore 文件进行签名外，还支持直接指定 pem 证书文件和私钥进行签名。</p> <p>关于 apksigner 的详细使用，可以参考开发者手册：https://developer.android.google.cn/studio/command-line/apksigner</p> <h3 id="签名方案-v3"><a href="#签名方案-v3" class="header-anchor">#</a> 签名方案—V3</h3> <p>在上一章中，我们重点了解了 ZIP 文件的结构以及 V2 签名原理，基于对 V2 签名方案的认识，我们再来看一下 V3 签名方案。</p> <h4 id="apk-签名方案-v3-分块"><a href="#apk-签名方案-v3-分块" class="header-anchor">#</a> APK 签名方案 V3 分块</h4> <p>V3 签名方案时 Android 9.0 时引入的签名解决方案，支持了密钥轮替。根据官网介绍，V3 签名方案与 V2 基本相似，只是在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构的信息，应用可以通过将 APK 文件过去的签名证书链接到现在签署应用时使用的证书，从而使用新签名证书来签署应用。我们与 V2 对比来看一下 APK 签名块 V3 的结构， 橙色部分为新增的结构信息。</p> <p><img src="G:%5C%E5%B7%A5%E4%BD%9C%E7%AC%94%E8%AE%B0%5Cmarkdown%5C2208191606.png" alt=""></p> <p>签名数据部分中的 proof-of-rotation 属性包含一个单链表，其中每个节点都包含用于为之前版本的应用签名的签名证书。该单链表按版本排序，最旧的签名证书对应于根节点。在构建 proof-of-rotation 数据结构时，系统会让每个节点中的证书为列表中的下一个证书签名，从而为每个新密钥提供证据来证明它应该与旧密钥一样可信。</p> <h4 id="签名校验"><a href="#签名校验" class="header-anchor">#</a> 签名校验</h4> <p>V3 的校验过程也与 V2 大致相同，只是 V3 在 V2 校验的基础上添加了对 SDK 版本校验和 proof-of-rotation 结构的校验，其校验过程如下：</p> <p><img src="G:%5C%E5%B7%A5%E4%BD%9C%E7%AC%94%E8%AE%B0%5Cmarkdown%5C2208191605.png" alt=""></p> <blockquote><p>开发者文档说明：<a href="https://source.android.google.cn/security/apksigning/v3" target="_blank" rel="noopener noreferrer">APK 签名方案 v3<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p></blockquote> <h3 id="签名方案-v4"><a href="#签名方案-v4" class="header-anchor">#</a> 签名方案—V4</h3> <blockquote><p>在传统的应用安装方案中，开发者通过 ADB（Android Debug Bridge）以有线或无线的方式与终端用户连接，或者用户从软件商店直接下载，然而该方案需要用户等待完整的安装包传输结束后才能启动安装，在这期间产生了不良的用户体验。</p> <p>增量安装技术是一种流式的安装方案：一旦安装包的核心文件传输完成便可启动应用。流式安装意味着允许优先传输核心数据以启动应用，并在后台流式传输剩余数据。</p> <p>在Android 11中，Google在内核中实现了增量文件系统用于对增量安装的支持。(详见 https://source.android.com/devices/architecture/kernel/incfs）</p> <p>这使得 Android os 可以通过 ADB 流式传输 APK。同时，Android 11 为了适应增量安装，添加了新的 v4签名方案。</p> <p>此方案不改变前代签名方案而是创建一种新的签名：基于 APK 所有字节数据计算出 Merkle 哈希树，并将Merkle 树的根哈希、盐值作为签名数据进行包完整性验证。新的签名数据保存在 .idsig 文件中并且在进行增量安装前必须为APK创建对应的 v4 签名文件。</p></blockquote> <p>Android 11 的</p> <p>签名方案 V4 是 Android 11 的签名方案，Android 11 在内核中实现了增量文件系统用于对增量安装的支持，关于增量安装可以参考这篇文章：<a href="https://blog.csdn.net/weixin_42600398/article/details/123034521" target="_blank" rel="noopener noreferrer">Android AAB增量安装<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。实现了 支持了流式传输。V4 签名基于根据 APK 的所有字节计算得出的 Merkle 哈希树。将Merkle 树的根哈希、盐值作为签名数据进行包完整性验证。其签名信息存储在单独的 <code>&lt;apk name&gt;.apk.idsig</code> 文件中并且在进行增量安装前必须为APK创建对应的 v4 签名文件。V4 签名需要 <a href="https://source.android.google.cn/security/apksigning/v2" target="_blank" rel="noopener noreferrer">V2<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> 或 <a href="https://source.android.google.cn/security/apksigning/v3" target="_blank" rel="noopener noreferrer">V3<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> 签名作为补充。</p> <p>关于 V4 签名的内容，可以直接参考开发者文档说明：<a href="https://source.android.google.cn/security/apksigning/v4" target="_blank" rel="noopener noreferrer">APK 签名方案 v4<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>，在此不再赘述。</p> <p>以上便是我对学习 Android 签名校验过程的一些内容整理和理解。当这些内容梳理完后，再看相关源码，顿觉豁然开朗！</p> <p>参考博客：</p> <ol><li><a href="https://blog.csdn.net/CrazyMo_/article/details/107742963" target="_blank" rel="noopener noreferrer">APK签名详解及Android Studio 在线调试系统App<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/zwjemperor/article/details/80877203" target="_blank" rel="noopener noreferrer">APK签名机制原理详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/qq_43278826/article/details/118436116" target="_blank" rel="noopener noreferrer">Zip文件格式详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/zwjemperor/article/details/81051120" target="_blank" rel="noopener noreferrer">V2签名机制详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://developer.android.google.cn/studio/command-line/apksigner#usage-sign" target="_blank" rel="noopener noreferrer">apksigner 的使用<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/chzphoenix/article/details/118180869" target="_blank" rel="noopener noreferrer">细说Android apk四代签名：V1、V2、V3、V4<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/qq_45272690/article/details/120576341" target="_blank" rel="noopener noreferrer">AndroidV1,V2,V3签名原理详解<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/qq_43278826/article/details/119237062" target="_blank" rel="noopener noreferrer">Android V2签名与校验原理分析<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://blog.csdn.net/weixin_42600398/article/details/123034521" target="_blank" rel="noopener noreferrer">Android AAB增量安装<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://source.android.google.cn/security/apksigning/v2" target="_blank" rel="noopener noreferrer">APK 签名方案 v2<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://source.android.google.cn/security/apksigning/v3" target="_blank" rel="noopener noreferrer">APK 签名方案 v3<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ol></div></section> <footer class="page-edit"><!----> <div class="last-updated"><span class="prefix">最近更新时间: </span> <span class="time">5/6/2023, 6:41:13 PM</span></div></footer> <!----> <div class="comments-wrapper"><!----></div></main></div> <!----></div> <ul class="sub-sidebar sub-sidebar-wrapper" style="width:12rem;" data-v-6986a797 data-v-6f8f7dda><li class="level-2" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#签名机制" class="sidebar-link reco-side-签名机制" data-v-6986a797>签名机制</a></li><li class="level-2" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#android签名机制" class="sidebar-link reco-side-android签名机制" data-v-6986a797>Android签名机制</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#keystore" class="sidebar-link reco-side-keystore" data-v-6986a797>keystore</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#密钥库生成" class="sidebar-link reco-side-密钥库生成" data-v-6986a797>密钥库生成</a></li><li class="level-2" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#android签名方案" class="sidebar-link reco-side-android签名方案" data-v-6986a797>Android签名方案</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#签名方案-v1" class="sidebar-link reco-side-签名方案-v1" data-v-6986a797>签名方案—V1</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#签名方案-v2" class="sidebar-link reco-side-签名方案-v2" data-v-6986a797>签名方案—V2</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#签名方案-v3" class="sidebar-link reco-side-签名方案-v3" data-v-6986a797>签名方案—V3</a></li><li class="level-3" data-v-6986a797><a href="/blogs/summary/2022/signature-mechanism.html#签名方案-v4" class="sidebar-link reco-side-签名方案-v4" data-v-6986a797>签名方案—V4</a></li></ul></div></div></div><div class="global-ui"><div class="back-to-ceiling" style="right:1rem;bottom:6rem;width:2.5rem;height:2.5rem;border-radius:.25rem;line-height:2.5rem;display:none;" data-v-c6073ba8 data-v-c6073ba8><svg t="1574745035067" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5404" class="icon" data-v-c6073ba8><path d="M526.60727968 10.90185116a27.675 27.675 0 0 0-29.21455937 0c-131.36607665 82.28402758-218.69155461 228.01873535-218.69155402 394.07834331a462.20625001 462.20625001 0 0 0 5.36959153 69.94390903c1.00431239 6.55289093-0.34802892 13.13561351-3.76865779 18.80351572-32.63518765 54.11355614-51.75690182 118.55860487-51.7569018 187.94566865a371.06718723 371.06718723 0 0 0 11.50484808 91.98906777c6.53300375 25.50556257 41.68394495 28.14064038 52.69160883 4.22606766 17.37162448-37.73630017 42.14135425-72.50938081 72.80769204-103.21549295 2.18761121 3.04276886 4.15646224 6.24463696 6.40373557 9.22774369a1871.4375 1871.4375 0 0 0 140.04691725 5.34970492 1866.36093723 1866.36093723 0 0 0 140.04691723-5.34970492c2.24727335-2.98310674 4.21612437-6.18497483 6.3937923-9.2178004 30.66633723 30.70611158 55.4360664 65.4791928 72.80769147 103.21549355 11.00766384 23.91457269 46.15860503 21.27949489 52.69160879-4.22606768a371.15156223 371.15156223 0 0 0 11.514792-91.99901164c0-69.36717486-19.13165746-133.82216804-51.75690182-187.92578088-3.42062944-5.66790279-4.76302748-12.26056868-3.76865837-18.80351632a462.20625001 462.20625001 0 0 0 5.36959269-69.943909c-0.00994388-166.08943902-87.32547796-311.81420293-218.6915546-394.09823051zM605.93803103 357.87693858a93.93749974 93.93749974 0 1 1-187.89594924 6.1e-7 93.93749974 93.93749974 0 0 1 187.89594924-6.1e-7z" p-id="5405" data-v-c6073ba8></path><path d="M429.50777625 765.63860547C429.50777625 803.39355007 466.44236686 1000.39046097 512.00932183 1000.39046097c45.56695499 0 82.4922232-197.00623328 82.5015456-234.7518555 0-37.75494459-36.9345906-68.35043303-82.4922232-68.34111062-45.57627738-0.00932239-82.52019037 30.59548842-82.51086798 68.34111062z" p-id="5406" data-v-c6073ba8></path></svg></div></div></div>
    <script src="/assets/js/app.371ed010.js" defer></script><script src="/assets/js/3.fe7e7b6d.js" defer></script><script src="/assets/js/1.adb5634c.js" defer></script><script src="/assets/js/25.61d7e926.js" defer></script>
  </body>
</html>
